package hasthi.container;

import hasthi.common.*;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.manager.ManagerContextDocument;
import hasthi.tools.analyze.XSLTTransformer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.UUID;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.globus.gsi.CertUtil;
import org.globus.gsi.GlobusCredential;
import org.globus.gsi.TrustedCertificates;
import org.globus.gsi.gssapi.GlobusGSSCredentialImpl;
import org.globus.gsi.ptls.PureTLSContext;
import org.ietf.jgss.GSSCredential;
import org.xmlpull.v1.builder.XmlDocument;
import org.xmlpull.v1.builder.XmlElement;

import xsul.XsulException;
import xsul.http_server.HttpMiniServer;
import xsul.http_server.HttpServerException;
import xsul.http_server.HttpServerRequest;
import xsul.http_server.HttpServerResponse;
import xsul.http_server.ServerSocketFactory;
import xsul.invoker.DynamicInfosetInvokerException;
import xsul.invoker.gsi.GsiInvoker;
import xsul.invoker.puretls.PuretlsInvoker;
import xsul.invoker.soap_over_http.SoapHttpDynamicInfosetInvoker;
import xsul.processor.DynamicInfosetProcessorException;
import xsul.processor.soap_over_http.SoapHttpDynamicInfosetProcessor;
import xsul.puretls_server_socket_factory.PuretlsServerSocketFactory;
import xsul.soap.SoapUtil;
import xsul.soap11_util.Soap11Util;
import xsul.soap12_util.Soap12Util;
import xsul.ws_addressing.WsaMessageInformationHeaders;
import xsul.xbeans_util.XBeansUtil;
import COM.claymoresystems.sslg.SSLPolicyInt;

public class XsulBasedSoapContainer extends SoapHttpDynamicInfosetProcessor implements
        CommunicationContianier, UnicastContianier {
    private static Log log = LogFactory.getLog(HasthiConstants.LOGGER_NAME);

    private final MessageProcessor messageProcessor;
    private HttpMiniServer server;
    private final BaseContext baseContext;
    private int port = 0;
    private boolean secure = false;

    public XsulBasedSoapContainer(BaseContext baseContext, MessageProcessor messageProcessor,int port) {
        this.messageProcessor = messageProcessor;
        this.baseContext = baseContext;
        this.port = port;
    }

    
    public XsulBasedSoapContainer(BaseContext baseContext, MessageProcessor messageProcessor) {
        this.messageProcessor = messageProcessor;
        this.baseContext = baseContext;
    }

    
    @Override
    public XmlDocument processHttpXml(final HttpServerRequest req, HttpServerResponse arg1,
            XmlDocument soapDoc) throws DynamicInfosetProcessorException {
        try {
            final XSULMessageContext messageContext = new XSULMessageContext(soapDoc,req.getPath());
            final WsaMessageInformationHeaders wsah = messageContext.getAddressingHeaders();
            final SoapUtil soapFragrance = messageContext.getSoapFragrance();
            
            //This code will forward a post request, but it must be a XML message, and we
            //do not honor WS-Addressing replyto here
           String forwardUrl = ContainerUtils.getForwardUrl(req.getPath());
           if(forwardUrl != null){
                SoapHttpDynamicInfosetInvoker invoker = createInvoker(forwardUrl);
                invoker.setLocation(forwardUrl);
                return invoker.invokeXml(soapDoc);
            } 

            // final String dn = findDNFromHttpRequest(req);
            if (wsah != null && SOAPUtils.checkIfAsyncReplyRequired(wsah)) {
                // We process the message in a seperate thread and return null
                Thread thead = new Thread(new Runnable() {
                    public void run() {
                        try {
                            XmlObject responseAsXmlbeans = messageProcessor.processMessage(messageContext);
                            XmlElement responseAsXml = XBeansUtil
                                    .xmlObjectToXmlElement(responseAsXmlbeans);
                            if(responseAsXml != null){
                                if(log.isTraceEnabled() && responseAsXml != null){
                                    log.trace("Sent:" +Utils.prettyPrint2String(responseAsXml));
                                }
                                SOAPUtils.sendAsyncResponse(soapFragrance, responseAsXml,wsah, false);    
                            }
                        } catch (Throwable e) {
                            e.printStackTrace();
                            log.error(e);
                            HasthiException error;
                            if (e instanceof HasthiException) {
                                error = (HasthiException) e;
                            } else {
                                error = new HasthiException(e,FaultCode.InternalServiceError);
                            }
                            XmlElement result = error.createFault(soapFragrance);
                            if(log.isTraceEnabled()){
                                log.trace("Sent:" +Utils.prettyPrint2String(result));
                            }
                            SOAPUtils.sendAsyncResponse(soapFragrance, result,wsah, true);
                        }
                    }

                });
                thead.start();
                return null;
            } else {
                // we process the thread in teh same thread
                try {
                    XmlObject responseAsXmlbeans = messageProcessor.processMessage(messageContext);
                    if(responseAsXmlbeans != null){
                        XmlElement responseAsXml = XBeansUtil.xmlObjectToXmlElement(responseAsXmlbeans);
                        return soapFragrance.wrapBodyContent(responseAsXml);    
                    }else{
                        return null;
                    }
                } catch (Throwable e) {
                    log.error("Error at "+getContainierAddress() + " "+e.getMessage(),e);
                    ErrorManager.error(e);
                    HasthiException error;
                    if (e instanceof HasthiException) {
                        error = (HasthiException) e;
                    } else {
                        error = new HasthiException(e,FaultCode.InternalServiceError);
                    }
                    return soapFragrance.wrapBodyContent(error.createFault(soapFragrance));
                }
            }
        } catch (Throwable e) {
            log.fatal(e);
            ErrorManager.error(e);
            throw new DynamicInfosetProcessorException(e.getMessage(), e);
        }
    }

    
    @Override
    public XmlElement processMessage(XmlElement arg0) throws DynamicInfosetProcessorException {
        return null;
    }

    /* (non-Javadoc)
     * @see hasthi.container.UnicastContianier#createCommunicationClient(hasthi.container.NodeAddress)
     */
    public CommunicationClient createCommunicationClient() throws HasthiException {
        return new SOAPCommunicationClient();
    }

    /* (non-Javadoc)
     * @see hasthi.container.UnicastContianier#send(hasthi.container.MessageContext)
     */
    public void send(MessageContext message) throws RemoteProcessorException,HasthiException {
        SOAPCommunicationClient client = new SOAPCommunicationClient();
        client.send(message);
    }

    /* (non-Javadoc)
     * @see hasthi.container.UnicastContianier#sendReceive(hasthi.container.MessageContext)
     */
    public XmlObject sendReceive(MessageContext message) throws RemoteProcessorException,HasthiException {
        SOAPCommunicationClient client = new SOAPCommunicationClient();
        return client.sendReceive(message);
    }

    /* (non-Javadoc)
     * @see hasthi.container.UnicastContianier#startContainer()
     */
    public void startContainer() throws HasthiException {
        try {
            if (secure) {
                PureTLSContext ctx = new PureTLSContext();
                String trustedCertsFile = baseContext.getTrustedCertsFile();
                String certKeyFile = baseContext.getHostKeyfile();

                // try to load host certificate
                if (trustedCertsFile != null && certKeyFile != null) {
                    ctx.loadRootCertificates(trustedCertsFile);
                    ctx.loadEAYKeyFile(certKeyFile, "");
                    SSLPolicyInt policy = new SSLPolicyInt();
                    policy.requireClientAuth(true);
                    policy.setAcceptNoClientCert(true);
                    ctx.setPolicy(policy);
                } else {
                    // Use Globous crednatials if it is there
                    GSSCredential gssCredntial = baseContext.getCredential();
                    if (gssCredntial instanceof GlobusGSSCredentialImpl) {
                        GlobusCredential globusCred = ((GlobusGSSCredentialImpl) gssCredntial)
                                .getGlobusCredential();
                        TrustedCertificates tc = TrustedCertificates
                                .getDefaultTrustedCertificates();
                        if (tc == null) {
                            throw new HasthiException("Trusted certificates is null",FaultCode.InitalizationError);
                        }
                        X509Certificate[] certs = tc.getCertificates();
                        ctx.setTrustedCertificates(certs);
                        ctx.setCredential(globusCred);
                    } else {
                        throw new HasthiException(
                                "Can not find the credantial to start a secure server",FaultCode.InitalizationError);
                    }
                }

                ServerSocketFactory secureSocketFactory = new PuretlsServerSocketFactory(port, ctx);
                server = new HttpMiniServer(secureSocketFactory);
            } else {
                server = new HttpMiniServer(port);
            }
            
            setServer(server);
            super.start();
            if(messageProcessor != null){
                messageProcessor.addContainer(this);    
            }
            System.out.println("Server started on port "+ server.getServerPort());
        } catch (HttpServerException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (DynamicInfosetProcessorException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (IOException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (GeneralSecurityException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        }
    }

    
    @Override
    public void service(HttpServerRequest req, HttpServerResponse res) throws HttpServerException {
        String path = req.getPath();
        try {
            String method = req.getMethod();
            // handle the GET request
            if (method.equals("GET")) {
                //This one exposes the files in resources/public_html to the outside. This is useful for
                //exposing java scripts ect
                
                //process forward requests, right now work only for GET requests
                //supported format is hasthi/forward/<protocol>_<host>_<port>/<path>
                if(ContainerUtils.handleFileRequests(path, res.getOutputStream())){
                    return;
                }
                String forwardUrl = ContainerUtils.getForwardUrl(req.getPath());
                if(forwardUrl != null){
                    SoapHttpDynamicInfosetInvoker invoker = createInvoker(forwardUrl);
                    String response = invoker.invokeHttpGet(forwardUrl);
                    res.getOutputStream().write(response.getBytes());
                    return;
                } 
                
                MessageContext messageContext = new SimpleMessageContext(null,path,"");
                XmlObject responseAsXmlbeans = messageProcessor.processMessage(messageContext);
                if(responseAsXmlbeans != null){
                    OutputStream out = res.getOutputStream();
                    if(responseAsXmlbeans instanceof ManagerContextDocument){
                      XSLTTransformer.transformWithXSLT(responseAsXmlbeans, "resources/xslt/system.xslt", out);
                      out.flush();
                    }else{
                        XmlCursor c = responseAsXmlbeans.newCursor();
                        c.toFirstContentToken();
                        QName name = c.getName();
                        if(name.getLocalPart().equals("html") || name.getLocalPart().equals("HTML")){
                            res.setContentType("text/html");
                        }else{
                            res.setContentType("text/xml");
                        }
                        responseAsXmlbeans.save(out);
                    }
                    out.flush();
                }
            } else {
                super.service(req, res); // invokes XML message processing
            }
        } catch (HasthiException e) {
            throw new HttpServerException("Error processing HTTP GET "+path,e);
        } catch (IOException e) {
            throw new HttpServerException("Error processing HTTP GET"+ path,e);
        }
    }
    /* (non-Javadoc)
     * @see hasthi.container.UnicastContianier#stopContainer()
     */
    public void stopContainer() throws HasthiException {
        if(server != null){
            server.shutdownServer();
            System.out.println("Server on port " + server.getServerPort() + " stopped");
        }
    }
    
    
    /* (non-Javadoc)
     * @see hasthi.container.UnicastContianier#getContainierAddress()
     */
    public String getContainierAddress() {
        return server.getLocation();
    }

    public class SOAPCommunicationClient implements CommunicationClient{
        private SoapHttpDynamicInfosetInvoker invoker;
        
        public SOAPCommunicationClient(){
        }
        
        public void send(MessageContext message) throws RemoteProcessorException,HasthiException {
            try {
                invoker = createInvoker(message.getAddress().getAddressAsString());
                XmlElement request = XBeansUtil.xmlObjectToXmlElement(message.getRequest());
                SoapUtil util = Soap11Util.getInstance();
                XmlDocument soapRequest = util.wrapBodyContent(request);
                WsaMessageInformationHeaders headers = new WsaMessageInformationHeaders(soapRequest);
                headers.setTo(new URI(message.getSoapEndpoint()));
                headers.setAction(new URI(message.getSoapAction()));
                headers.setMessageId(new URI(UUID.randomUUID().toString()));
                invoker.setLocation(message.getSoapEndpoint());
                invoker.invokeXml(soapRequest); 
            } catch (URISyntaxException e) {
                throw new HasthiException(e,FaultCode.ErrorAtClientBeforeWsCall);
            }catch(DynamicInfosetInvokerException e){
                throw new HasthiException(e,FaultCode.ErrorAtClientWhileWsCall);
            }
        }

        public XmlObject sendReceive(MessageContext message) throws RemoteProcessorException,HasthiException {
            try {
                invoker = createInvoker(message.getSoapEndpoint());
                if(message.getRequest() == null){
                    String httpGetResponse = invoker.invokeHttpGet(message.getSoapEndpoint());
                    System.out.println(httpGetResponse);
                    return XmlObject.Factory.parse(httpGetResponse);
                }
                
                XmlElement request = XBeansUtil.xmlObjectToXmlElement(message.getRequest());
                
                SoapUtil util = Soap11Util.getInstance();
                XmlDocument soapRequest = util.wrapBodyContent(request);
                WsaMessageInformationHeaders headers = new WsaMessageInformationHeaders(soapRequest);
                headers.setTo(new URI(message.getSoapEndpoint()));
                headers.setAction(new URI(message.getSoapAction()));
                headers.setMessageId(new URI(Utils.createUUID()));
                invoker.setLocation(message.getSoapEndpoint());

                //Utils.prettyPrint(soapRequest.getDocumentElement());
                XmlDocument soapResponse = invoker.invokeXml(soapRequest);
                
                if(soapResponse == null){
                    return null;
                }
                
                SoapUtil[] utils = new SoapUtil[]{Soap11Util.getInstance(),
                        Soap12Util.getInstance()};
                util = SoapUtil.selectSoapFragrance(soapResponse, utils);
                XmlElement result = util.requiredBodyContent(soapResponse);
                XmlObject responseAsXbeans  = XBeansUtil.xmlElementToXmlObject(result);
                
                if(SOAPUtils.isFaultMessage(responseAsXbeans)){
                    System.out.println(XmlBeanUtils.prettyPrint(responseAsXbeans));
                    throw HasthiException.buildException(responseAsXbeans);
                }else{
                    return responseAsXbeans;
                }
            } catch (URISyntaxException e) {
                throw new HasthiException(e,FaultCode.ErrorAtClientBeforeWsCall);
            }catch(DynamicInfosetInvokerException e){
                throw new HasthiException(e,FaultCode.ErrorAtClientWhileWsCall);
            } catch (XmlException e) {
                throw new HasthiException(e,FaultCode.ErrorAtClientWhileWsCall);
            }catch (Throwable e) {
                throw new HasthiException("Failed to send message to "+ message.getSoapEndpoint() + ":"
                        +e.getMessage(),e,FaultCode.ErrorAtClientWhileWsCall);
            }
        }
    }
    
    public SoapHttpDynamicInfosetInvoker createInvoker(String endpointAddress) throws HasthiException{
        if(endpointAddress.startsWith("https")){
            return createSecureInvoker(baseContext, null);
        }else{
           return new SoapHttpDynamicInfosetInvoker(); 
        }
    }
    
    public static SoapHttpDynamicInfosetInvoker createSecureInvoker(BaseContext context,GSSCredential sessionCredentail) throws HasthiException{
        try {
            String trustedCertFile = context.getTrustedCertsFile();
            String hostKeyfile = context.getHostKeyfile();
            GSSCredential credential = sessionCredentail;
            if(credential == null){
                credential = context.getCredential();   
            }
            
            SoapHttpDynamicInfosetInvoker invoker;
            if(trustedCertFile != null && hostKeyfile != null){
                invoker = new PuretlsInvoker(hostKeyfile,"",trustedCertFile);
            }else if(credential != null){
                if(context.getTrustedCertsFile() != null){
                    X509Certificate[] certs = null;
                    certs = CertUtil.loadCertificates(trustedCertFile);
                    invoker = new GsiInvoker(credential,certs);
                }else{
                    invoker = new GsiInvoker(credential);    
                }
            }else{
                throw new HasthiException("Can not create a secure Invoker, no credentails provided",FaultCode.InitalizationError);
            }
            return invoker;
        } catch (XsulException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (IOException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (GeneralSecurityException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        }
    }
    public String doRESTCall(String url,Map<String, String> parameters,String method) throws HasthiException{
        SoapHttpDynamicInfosetInvoker invoker = createInvoker(url);
        return invoker.invokeHttpGet(url);
    }

    public void setSecure(boolean secure) {
        this.secure = secure;
    }
}
